package trunk.src;

import java.util.ArrayList;
import java.util.List;

import trunk.src.Exp.EseqExp;
import trunk.src.Exp.Exp;
import trunk.src.Exp.IdExp;
import trunk.src.Exp.NumExp;
import trunk.src.Exp.OpExp;
import trunk.src.ExpList.ExpList;
import trunk.src.ExpList.LastExpList;
import trunk.src.ExpList.PairExpList;
import trunk.src.Stm.AssignStm;
import trunk.src.Stm.CompoundStm;
import trunk.src.Stm.PrintStm;
import trunk.src.Stm.Stm;
import trunk.src.Table.IntAndTable;
import trunk.src.Table.Table;

/**
 * The Class Main.
 */
public class Main {

	/** The print size list, has all prints size. */
	private static List<Integer> printSizeList = new ArrayList<Integer>();

	/**
	 * The main method.
	 * 
	 * @param args
	 *            the arguments
	 */
	public static void main(String[] args) {
		Stm print1 = new PrintStm(new PairExpList(new IdExp("a"),
				new PairExpList(new IdExp("a"), new PairExpList(new IdExp("a"),
						new PairExpList(new IdExp("a"), new LastExpList(
								new IdExp("a")))))));

		Stm print2 = new PrintStm(new PairExpList(new IdExp("a"),
				new PairExpList(new IdExp("a"), new PairExpList(new IdExp("a"),
						new LastExpList(new IdExp("a"))))));

		Stm print3 = new PrintStm(new PairExpList(new IdExp("a"),
				new LastExpList(new IdExp("a"))));

		Stm prog = new CompoundStm(new AssignStm("a", new OpExp(new NumExp(5),
				OpExp.Plus, new NumExp(3))), new CompoundStm(new AssignStm("b",
				new EseqExp(new PrintStm(new PairExpList(new EseqExp(print1,
						new IdExp("a")), new PairExpList(new EseqExp(print3,
						new IdExp("a")), new LastExpList(new EseqExp(print2,
						new IdExp("a")))))), new OpExp(new NumExp(10),
						OpExp.Times, new IdExp("a")))), new PrintStm(
				new LastExpList(new IdExp("b")))));

		findAllPrintsInStm(prog);
		final Integer n = findMaxPrint();
		System.out.println("The Maximun Number of Print Arguments : " + n);

		System.out.println("Executando o programa: ");
		try {
			interpStm(prog, null);
		} catch (Exception e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Find the max print size in within the printSizeList.
	 * 
	 * @return the referred max print size
	 */
	private static Integer findMaxPrint() {
		Integer maxPrint = 0;

		for (Integer printSize : printSizeList) {
			if (maxPrint.compareTo(printSize) < 0) {
				maxPrint = printSize;
			}
		}

		return maxPrint;
	}

	/**
	 * Find all prints within a statement.
	 * 
	 * @param prog
	 *            the statement
	 */
	private static void findAllPrintsInStm(Stm prog) {
		if (prog instanceof PrintStm) {
			printSizeList.add(calculatePrintArgs(((PrintStm) prog).exps));
			findAllPrintsInExpList(((PrintStm) prog).exps);
		} else if (prog instanceof AssignStm) {
			findAllPrintsInExp(((AssignStm) prog).exp);
		} else if (prog instanceof CompoundStm) {
			findAllPrintsInStm(((CompoundStm) prog).stm1);

			findAllPrintsInStm(((CompoundStm) prog).stm2);
		}
	}

	/**
	 * Find all prints within a expression.
	 * 
	 * @param prog
	 *            the expression
	 */
	private static void findAllPrintsInExp(Exp prog) {
		if (prog instanceof IdExp || prog instanceof NumExp) {
			return;
		} else if (prog instanceof EseqExp) {
			findAllPrintsInExp(((EseqExp) prog).exp);
			findAllPrintsInStm(((EseqExp) prog).stm);
		} else if (prog instanceof OpExp) {
			findAllPrintsInExp(((OpExp) prog).left);
			findAllPrintsInExp(((OpExp) prog).right);
		}
	}

	/**
	 * Find all prints within a expression list.
	 * 
	 * @param prog
	 *            the expression list
	 */
	private static void findAllPrintsInExpList(ExpList prog) {
		if (prog instanceof LastExpList) {
			findAllPrintsInExp(((LastExpList) prog).head);
		} else if (prog instanceof PairExpList) {
			findAllPrintsInExp(((PairExpList) prog).head);
			findAllPrintsInExpList(((PairExpList) prog).tail);
		}
	}

	/**
	 * Calculates the number of print args of the given expression list.
	 * 
	 * @param exps
	 *            the expression list
	 * @return the integer
	 */
	private static Integer calculatePrintArgs(ExpList exps) {
		if (exps instanceof LastExpList) {
			return 1;
		} else if (exps instanceof PairExpList) {
			return 1 + calculatePrintArgs(((PairExpList) exps).tail);
		}
		return 0;
	}

	/**
	 * Interprets the statement (stm) for the given table (t).
	 * 
	 * @param s
	 *            the s
	 * @param t
	 *            the t
	 * @return the table
	 * @throws Exception
	 *             the exception
	 */
	private static Table interpStm(Stm s, Table t) throws Exception {
		if (s instanceof AssignStm) {
			IntAndTable intAndTable = interpExp(((AssignStm) s).exp, t);

			return update(intAndTable.getTable(), ((AssignStm) s).id,
					intAndTable.getIntValue());
		} else if (s instanceof CompoundStm) {
			Table table1 = interpStm(((CompoundStm) s).stm1, t);
			return interpStm(((CompoundStm) s).stm2, table1);
		} else if (s instanceof PrintStm) {
			IntAndTable intAndTable = printExpList(((PrintStm) s).exps, t);
			System.out.println();
			return intAndTable.getTable();
		}
		return null;
	}

	/**
	 * Prints the expression list given (exps).
	 * 
	 * @param exps
	 *            the exps
	 * @param t
	 *            the t
	 * @return the int and table
	 * @throws Exception
	 *             the exception
	 */
	private static IntAndTable printExpList(ExpList exps, Table t)
			throws Exception {
		if (exps instanceof LastExpList) {
			IntAndTable intAndTable = interpExp(((LastExpList) exps).head, t);
			System.out.print(intAndTable.getIntValue() + " ");
			return intAndTable;
		} else if (exps instanceof PairExpList) {
			IntAndTable intAndTable = interpExp(((PairExpList) exps).head, t);
			System.out.print(intAndTable.getIntValue() + " ");

			return printExpList(((PairExpList) exps).tail, intAndTable
					.getTable());
		}
		return null;

	}

	/**
	 * Update the table with the given key and value (key = value).
	 * 
	 * @param t
	 *            the t
	 * @param key
	 *            the key
	 * @param value
	 *            the value
	 * @return the table
	 */
	private static Table update(Table t, String key, Integer value) {
		return new Table(key, value, t);
	}

	/**
	 * Interprets the expression (exp) with the given table.
	 * 
	 * @param e
	 *            the e
	 * @param t
	 *            the t
	 * @return the value of the rigth expression and the table with the left
	 *         statement executed
	 * @throws Exception
	 *             thrown if the given key isn't found
	 */
	private static IntAndTable interpExp(Exp e, Table t) throws Exception {
		if (e instanceof EseqExp) {
			Table table1 = interpStm(((EseqExp) e).stm, t);
			return interpExp(((EseqExp) e).exp, table1);
		} else if (e instanceof IdExp) {
			Integer idValue = t.lookup(((IdExp) e).id);
			if (idValue == null) {
				throw new Exception("key " + ((IdExp) e).id + " not found");
			}

			return new IntAndTable(idValue, t);
		} else if (e instanceof NumExp) {
			return new IntAndTable(((NumExp) e).num, t);
		} else if (e instanceof OpExp) {
			IntAndTable left = interpExp(((OpExp) e).left, t);
			IntAndTable rigth = interpExp(((OpExp) e).right, t);
			Integer result = doTheMath(left.getIntValue(), ((OpExp) e).oper,
					rigth.getIntValue());
			return new IntAndTable(result, t);
		}
		return null;
	}

	/**
	 * Do the math with the left operatorand, the rigth operatorand and the
	 * operator.
	 * 
	 * @param leftValue
	 *            the left value
	 * @param oper
	 *            the oper
	 * @param rigthValue
	 *            the rigth value
	 * @return the value of the given mathematical expression
	 */
	private static Integer doTheMath(Integer leftValue, Integer oper,
			Integer rigthValue) {
		if (oper.equals(OpExp.Div)) {
			return leftValue / rigthValue;
		} else if (oper.equals(OpExp.Times)) {
			return leftValue * rigthValue;
		} else if (oper.equals(OpExp.Minus)) {
			return leftValue - rigthValue;
		} else if (oper.equals(OpExp.Plus)) {
			return leftValue + rigthValue;
		}
		return null;
	}
}
